﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CriptoHash.Criptografia
{
    public class RandomGenerator
    {
        public enum MetodoRandom {QUADRADO_DO_MEIO, LINEAR_CONGRUENTE};

        private int _semente;
        public int Semente
        {
            get { return _semente; }
            set { _semente = value; }
        }

        private int _n;
        public int N
        {
            get { return _n; }
            set { _n = value; }
        }

        public RandomGenerator(int Semente)
        {
            this.Semente = Semente;
        }

        public RandomGenerator(int Semente, int N) : this(Semente)
        {
            this.N = N;
        }

        public int NumeroAleatorio(MetodoRandom Metodo)
        {
            int random = 0;

            switch (Metodo)
            {
                case MetodoRandom.QUADRADO_DO_MEIO:
                    {
                        //Salvamos o número da semente inicial em uma string para verificar o número de casas decimais
                        string textoSementeOriginal = Semente + "";

                        //Multiplicamos a semente por ela mesmo (ou seja, o quadrado da semente)
                        int sementeQuadrado = Semente * Semente;

                        //Depois passamos para uma string para mexer com as posições
                        string textoSemente = sementeQuadrado + "";

                        //Precisamos pegar os valores do meio dessas string e esse será o valor

                        //Para isso, precisamos saber o tamanho (nº de caracteres) do textoSemente
                        int tamanhoTextoSemente = textoSemente.Length;
                        if (tamanhoTextoSemente < 6)
                        {
                            textoSemente = "0" + textoSemente;
                            tamanhoTextoSemente++;
                        }

                        //O número random é do tamanho da metado do valor da semente
                        int tamanhoNumero = (int)Math.Floor(((double)tamanhoTextoSemente) / 2);

                        //O número de passos para chegar ao centro da string textoSemente é metade do tamanhoNumero
                        int passos = (int)Math.Ceiling((double)tamanhoNumero / 2);

                        string valorString = "";

                        for (int i = passos; i < passos + tamanhoNumero; i++)
                        {
                            valorString += textoSemente[i];
                        }

                        random = int.Parse(valorString);
                        Semente = random;
                        break;
                    }
                case MetodoRandom.LINEAR_CONGRUENTE:
                    {
                        //A fórmula do método linear congruente é uma fórmula de recorrência dada por:
                        //Xn+1 = (aXn + c) mod m, n >= 0
                        //Sendo X0 a semente
                        //a = multiplicador
                        //c = incremento
                        //m = período ou módulo

                        //C e M devem ser primos entre si
                        //Seja b = a - 1; Seja P qualquer número primo divisor de M
                        //b deve ser múltiplo de P
                        //Se m for múltiplo de 4, b também deve ser

                        //O valor de N deve ser passado por parâmetro no construtor

                        if (N == 0)
                        {
                            random = int.MinValue;
                        }
                        else
                        {
                            //Fórmula para o cálculo do m
                            //m =( MenorPrimo(sqrt(10 * n))**2

                            int m = (int)Math.Pow((double)Matematica.MenorPrimo((int)Math.Sqrt(10 * N)), (double)2);

                            int q = Matematica.MenorPrimo((int)Math.Sqrt(10 * N));

                            int a = q + 1;

                            //Arbitrário
                            int c = (int)((double)m / 9);
                            if (c == q)
                            {
                                c--;
                            }

                            random = LinearCongruente(N, m, a, c);
                            Semente = random;
                        }

                        break;
                    }
            }
            return random;
        }

        //Xn+1 = (aXn + c) mod m, n >= 0
        private int LinearCongruente(int n, int m, int a, int c)
        {
            if (n == 0)
            {
                return Semente;
            }
            else
            {
                return (a * LinearCongruente(--n, m, a, c) + c) % m;
            }
        }
    }
}
